/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat;

import static br.ufpe.cin.imlm2.util.options.OptionsParser.PARAM_PREFIX;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import soot.PackManager;
import soot.SceneTransformer;
import soot.Transform;

import br.ufpe.cin.imlm2.jbeat.soot.icpoints.TrapInspector;
import br.ufpe.cin.imlm2.jbeat.soot.report.ExceptionReporter;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.generation.GeneratesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.generation.RaisesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.propagation.PropagatesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.traps.EmptyTrapRemover;
import br.ufpe.cin.imlm2.util.BuildClasspath;
import br.ufpe.cin.imlm2.util.Parameterizable;
import br.ufpe.cin.imlm2.util.ParsingException;
import br.ufpe.cin.imlm2.util.SystemProperties;
import br.ufpe.cin.imlm2.util.options.IOptions;
import br.ufpe.cin.imlm2.util.options.NoSuchOptionException;
import br.ufpe.cin.imlm2.util.options.Option;
import br.ufpe.cin.imlm2.util.options.OptionsParser;

public class Main {

	private static final SceneTransformer EMPTY_TRAP_REMOVER = new EmptyTrapRemover();
	private static final SceneTransformer TRAP_INSPECTOR = new TrapInspector();
	private static final RaisesCalculator RAISES_CALCULATOR = new RaisesCalculator();
	private static final GeneratesCalculator GENERATES_CALCULATOR = new GeneratesCalculator();
	private static final PropagatesCalculator PROPAGATES_CALCULATOR = new PropagatesCalculator();
	private static final ExceptionReporter EXCEPTION_REPORTER = new ExceptionReporter();

	private static final Transform EMPTY_TRAP_REMOVER_TRANSFORM = new Transform(
			"wjtp.etr", EMPTY_TRAP_REMOVER);
	private static final Transform TRAP_INSPECTOR_TRANSFORM = new Transform(
			"wjtp.ti", TRAP_INSPECTOR);
	private static final Transform RAISES_CALCULATOR_TRANSFORM = new Transform(
			"wjtp.rc", RAISES_CALCULATOR);
	private static final Transform GENERATES_CALCULATOR_TRANSFORM = new Transform(
			"wjtp.gc", GENERATES_CALCULATOR);
	private static final Transform PROPAGATES_CALCULATOR_TRANSFORM = new Transform(
			"wjtp.pc", PROPAGATES_CALCULATOR);
	private static final Transform EXCEPTION_REPORTER_TRANSFORM = new Transform(
			"wjtp.er", EXCEPTION_REPORTER);

	private static final List<Transform> TRANSFORMS = new ArrayList<Transform>();
	static {
		TRANSFORMS.add(EXCEPTION_REPORTER_TRANSFORM);
		TRANSFORMS.add(GENERATES_CALCULATOR_TRANSFORM);
		TRANSFORMS.add(PROPAGATES_CALCULATOR_TRANSFORM);
		TRANSFORMS.add(RAISES_CALCULATOR_TRANSFORM);
	}

	public static void printUsage() {
		StringBuffer phaseOptions = new StringBuffer();
		for (Option op : OptionsParser.getInstance().getOptions()) {
			if (!op.getName().equals("enabled")) {
				phaseOptions.append('\t').append("--").append(op.getName());
				if (!op.getName().equals(op.getAlias()))
					phaseOptions.append('(').append(op.getAlias()).append(')');
				String defVal = op.getDefaultValue().toString();
				if (op.getType().equals(List.class)) {
					@SuppressWarnings("unchecked")
					List<Object> list = (List<Object>) op.getDefaultValue();
					defVal = "";
					for (Object object : list) {
						defVal += object.toString() + ",";
					}
					if (!defVal.isEmpty())
						defVal = defVal.substring(0, defVal.length() - 1);
				}
				phaseOptions.append(':').append("'").append(defVal).append("'");
				if (op.getDescription() != null)
					phaseOptions.append(" - ").append(op.getDescription())
							.append(SystemProperties.LINE_SEPARATOR);
			}
		}
		if (phaseOptions.length() > 0)
			phaseOptions.deleteCharAt(phaseOptions.length() - 1);
		System.out
				.println("Usage:\n"
						+ " java -jar jbeat.jar MainClass [jbeat_args | soot_args]* \n"
						+ " Where jbeat_args :\n"
						+ phaseOptions.toString()
						+ "\tNOTES:\n- Arguments are CASE SENSITIVE (i.e. true !=  TrUe).\n- Unknown arguments will be passed as arguments to Soot.\n"
						+ "- If the target.java.libraries system property is defined then it should contain a list of classpath "
						+ "entries (directories and jars separated by OS specific path separators) of the Java Runtime Libraries that are going to be used by the target program."
						+ " In this case the Java Runtime Libraries should NOT be included in the "
						+ PARAM_PREFIX + "classpath" + " paramater.\n"
						+ "- Lists are SEMI-COLLON separated.");
	}

	public static void main(String[] args) throws ParsingException,
			NoSuchOptionException {
		registerParserOptions();
		if (args.length > 0) {
			OptionsParser parser = OptionsParser.getInstance();
			parser.parse(args);
			if (!parser.isShowUsage()) {

				registerTransformersOptions();

				PackManager.v().getPack("wjtp")
						.add(EMPTY_TRAP_REMOVER_TRANSFORM);

				if (parser.isHandlerInspection()) {
					PackManager.v().getPack("wjtp")
							.add(TRAP_INSPECTOR_TRANSFORM);
				}

				if (parser.isAnalyzeRuntime()) {
					PackManager.v().getPack("wjtp")
							.add(GENERATES_CALCULATOR_TRANSFORM);
				}

				PackManager.v().getPack("wjtp")
						.add(RAISES_CALCULATOR_TRANSFORM);

				PackManager.v().getPack("wjtp")
						.add(PROPAGATES_CALCULATOR_TRANSFORM);
				PackManager.v().getPack("wjtp")
						.add(EXCEPTION_REPORTER_TRANSFORM);
				soot.Main.main(buildSootCommandLine().split(" "));

				if (parser.isCleanSootOutput())
					deleteSootOutput();

			} else {
				printUsage();
			}
		} else {
			printUsage();
		}

	}

	private static void registerParserOptions() {
		IOptions parserOps = OptionsParser.getInstance();
		for (Transform transform : TRANSFORMS) {
			IOptions opts = ((Parameterizable) transform.getTransformer())
					.getOptions();
			for (Option option : opts.getOptions()) {
				parserOps.addOption(option);
			}
		}

	}

	private static void deleteSootOutput() {
		File sootOutput = new File("sootOutput");
		if (sootOutput.exists() && sootOutput.isDirectory()) {
			deleteDirectory(sootOutput);
		}

	}

	private static void deleteDirectory(File dir) {
		for (File f : dir.listFiles()) {
			if (f.isDirectory())
				deleteDirectory(f);
			else
				f.delete();
		}
		dir.delete();
	}

	private static String buildTargetClassPath(List<String> cp) {
		List<String> targetCP = cp;
		if (SystemProperties.TARGET_JAVA_LIBRARIES != null) {
			String[] dirs = SystemProperties.TARGET_JAVA_LIBRARIES
					.split(SystemProperties.PATH_SEPARATOR);
			for (String dir : dirs) {
				File directory = new File(dir);
				targetCP.addAll(BuildClasspath.buildFromCPEntries(directory,
						false));

			}
		}
		StringBuffer classpath = new StringBuffer();
		for (String path : targetCP) {
			classpath.append(path).append(SystemProperties.PATH_SEPARATOR);
		}
		if (classpath.length() > 0) {
			classpath.delete(
					classpath.lastIndexOf(SystemProperties.PATH_SEPARATOR),
					classpath.length());
		}
		return classpath.toString();
	}

	private static void registerTransformersOptions() {
		GENERATES_CALCULATOR_TRANSFORM
				.setDeclaredOptions(buildDeclareOptions(GENERATES_CALCULATOR
						.getOptions()));
		RAISES_CALCULATOR_TRANSFORM
				.setDeclaredOptions(buildDeclareOptions(RAISES_CALCULATOR
						.getOptions()));
		PROPAGATES_CALCULATOR_TRANSFORM
				.setDeclaredOptions(buildDeclareOptions(PROPAGATES_CALCULATOR
						.getOptions()));
		EXCEPTION_REPORTER_TRANSFORM
				.setDeclaredOptions(buildDeclareOptions(EXCEPTION_REPORTER
						.getOptions()));
	}

	private static String buildDeclareOptions(IOptions opts) {
		StringBuffer phaseOptions = new StringBuffer();
		for (String opt : opts.getDeclaredOptions()) {
			phaseOptions.append(opt).append(' ');
		}
		if (phaseOptions.length() > 0)
			phaseOptions.deleteCharAt(phaseOptions.length() - 1);
		return phaseOptions.toString();
	}

	private static String buildSootCommandLine() throws NoSuchOptionException {
		OptionsParser parser = OptionsParser.getInstance();
		StringBuffer sootOptions = new StringBuffer();

		sootOptions.append(parser.getMainClass()).append(' ');

		// Main class
		sootOptions.append("-main-class").append(' ')
				.append(parser.getMainClass()).append(' ');

		// General options
		sootOptions.append("-w").append(' ');
		sootOptions.append("-app").append(' ');
		sootOptions.append("-output-format").append(' ').append('J')
				.append(' ');

		if (parser.isVerbose())
			sootOptions.append("-verbose").append(' ');

		if (parser.isTime())
			sootOptions.append("-time").append(' ');

		if (parser.isAllowMissingDependencies())
			sootOptions.append("-allow-phantom-refs").append(' ');

		if (SystemProperties.TARGET_JAVA_LIBRARIES == null)
			sootOptions.append("-prepend-classpath").append(' ');
		String cp = buildTargetClassPath(parser.getClasspath());
		sootOptions.append("-cp").append(' ').append(cp).append(' ');

		// Cg construction options
		sootOptions.append("-p").append(' ').append("cg").append(' ')
				.append("all-reachable").append(':').append("true").append(' ');
		sootOptions.append("-p").append(' ').append("cg").append(' ')
				.append("implicit-entry").append(':').append("entry:false")
				.append(' ');

		for (Transform transform : TRANSFORMS) {
			IOptions opts = ((Parameterizable) transform.getTransformer())
					.getOptions();
			for (String option : opts.getDeclaredOptions()) {
				sootOptions.append("-p").append(' ')
						.append(transform.getPhaseName()).append(' ')
						.append(option).append(':')
						.append(parser.getOptionValue(option)).append(' ');

			}
		}
		System.err.println("Soot command-line:");
		System.err.println(sootOptions.toString());
		return sootOptions.toString();
	}
}
